﻿//===============================================================================
// SharePoint911 - http://www.SharePoint911.com
// SharePoint ActiveX Override (SPAXO)
// Created By: Larry J. Riemann
//===============================================================================
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================

using System;
using System.Text;
using System.IO;
using SharePoint911.SPAXO.Properties;

namespace SharePoint911.SPAXO
{
    /// <summary>
    /// Stream used to insert a single item into the stream as it is being written
    /// </summary>
    public class InsertContentFilterStream : Stream
    {
        #region Private Members

        private Stream _baseStream = null;
        private Encoding _encoding = null;
        private bool _hasStringBeenInserted;
        private string _bufferHoldOver = "";
        private string _stringToInsert = null;
        private string _searchTerm = null;

        #endregion

        #region Constructors

        public InsertContentFilterStream(Stream baseStream, Encoding encode, string searchTerm, string dataToInsert)
        {
            _baseStream = baseStream;
            _encoding = encode;
            _searchTerm = searchTerm;
            _stringToInsert = dataToInsert;
        }

        #endregion

        #region Public Properties

        public override bool CanRead
        {
            get { return _baseStream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return _baseStream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return _baseStream.CanWrite; }
        }

        public override void Flush()
        {
            _baseStream.Flush();
        }

        public override long Length
        {
            get { return _baseStream.Length; }
        }

        public override long Position
        {
            get { return _baseStream.Position; }
            set { _baseStream.Position = value; }
        }

        #endregion

        #region Public Methods

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _baseStream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _baseStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _baseStream.SetLength(value);
        }

        /// <summary>
        /// Intercepts data being written to the underlying stream and inserts the string specified in the constructor
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public override void Write(byte[] buffer, int offset, int count)
        {
            // Check to see if the data has already been inserted into this instance of the stream
            if (_hasStringBeenInserted)
            {
                // If the string has already been inserted no need to look again, just right to the underlying stream

                // Write the contents of the unchanged buffer to the underlying stream
                _baseStream.Write(buffer, offset, count);
            }
            else
            {
                // If the string has not yet been inserted, search and insert if item is found

                // Setup string builder to rebuffer the data being written to the stream
                string data = _bufferHoldOver + _encoding.GetString(buffer, offset, count);
                StringBuilder stringBuffer = new StringBuilder(data.Length + _stringToInsert.Length + _bufferHoldOver.Length);
                
                // Reset the holdover buffer
                _bufferHoldOver = "";
                
                // Add main data to the buffer
                stringBuffer.Append(data);

                // Search for the item in the data
                int searchIndex = data.IndexOf(_searchTerm, StringComparison.OrdinalIgnoreCase);

                if (searchIndex == -1)
                {
                    // If the item was not found check the end of the buffer for a possible split of the search term

                    // Get the length of the search term minus one character since the full term has already been checked
                    int searchItemAdjustedLength = _searchTerm.Length - 1;

                    // Get a reference to the characters at the end of the buffer
                    int startIndex = (data.Length > searchItemAdjustedLength) ? data.Length - searchItemAdjustedLength : 0;
                    searchItemAdjustedLength = (data.Length > searchItemAdjustedLength) ? searchItemAdjustedLength : data.Length;
                    string endOfBufferText = stringBuffer.ToString(startIndex, searchItemAdjustedLength);

                    // Search though the text at the end of the buffer with an adjusted search term
                    // each iteration will adjust the start of the search and the search term being evaluated
                    for (int i = 0; i < searchItemAdjustedLength; i++)
                    {
                        // Check to see if the portion of the end of buffer matched the portion of the search term
                        if (endOfBufferText.Substring(i, searchItemAdjustedLength - i) == _searchTerm.Substring(0, searchItemAdjustedLength - i))
                        {
                            // If a match is found, place the portion of the term found into a holding location to use on the very next write
                            _bufferHoldOver = endOfBufferText.Substring(i, searchItemAdjustedLength - i);

                            // Remove matched data from the end of the buffer
                            stringBuffer.Remove(stringBuffer.Length - _bufferHoldOver.Length, _bufferHoldOver.Length);
                            break;
                        }
                    }
                }
                else
                {
                    // If the item was found insert the javascript before it
                    stringBuffer.Insert(searchIndex, _stringToInsert);
                    _hasStringBeenInserted = true;
                }

                // Write the contents of the buffer to the underlying stream
                _baseStream.Write(_encoding.GetBytes(stringBuffer.ToString()), 0, stringBuffer.Length);
            }
        }

        #endregion
    }
}